package com.zhongfl.crm.util;

import com.xiaoleilu.hutool.bean.BeanUtil;
import com.xiaoleilu.hutool.date.DateUtil;
import com.zhongfl.crm.annotation.FieldMeta;
import com.zhongfl.crm.exception.CRMException;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.springframework.stereotype.Service;

import java.beans.IntrospectionException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.*;

@Service
@Slf4j
public class ObjectCompareUtils {

    private static List<String> excludeFieldNames;


    static {
        excludeFieldNames = Arrays.asList("createdBy",
            "createdTime",
            "modifiedBy",
            "modifiedTime",
            "class",
            "staffCode");
    }

    public void addExcludeField(String fieldName) {
        if (!excludeFieldNames.contains(fieldName)) {
            excludeFieldNames.add(fieldName);
        }
    }

    public void removeExcludeField(String fieldName) {
        if (excludeFieldNames.contains(fieldName)) {
            excludeFieldNames.remove(fieldName);
        }
    }

    /**
     * 解析一下显示的字段名称
     *
     * @param field
     * @return
     */
    private String resolveFieldName(Field field) {
        String fieldName = field.getName();
        FieldMeta fieldMeta = field.getAnnotation(FieldMeta.class);
        if (null != fieldMeta) {
            String name = fieldMeta.name();
            if (StringUtils.isEmpty(name)) {
                throw new CRMException("field: " + field.getName() + "注解FieldMeta必须指定name值");
            }
            fieldName = name;
        }
        return fieldName;
    }

    /**
     * 对比两个对象差别
     *
     * @param source 原对象
     * @param target 现对象
     * @return 差异部分
     * @throws IntrospectionException
     */
    @SuppressWarnings("unchecked")
    public ObjectCompareResult compare(Object source, Object target) throws IntrospectionException {
        return compare(source, target, null);
    }

    /**
     * 比较两个对象差别, 只比较字段上有指定注解的字段
     *
     * @param source
     * @param target
     * @param compareAnnotationType 标识比较的注解
     * @return
     * @throws IntrospectionException
     */
    public ObjectCompareResult compare(Object source, Object target, final Class<? extends Annotation> compareAnnotationType) throws IntrospectionException {

        ObjectCompareResult result = new ObjectCompareResult();

        List<ObjectCompareFieldResult> diffs = new LinkedList<>();
        result.setDiffFields(diffs);

        List<Field> allFieldsList;
        if (null == compareAnnotationType) {
            allFieldsList = FieldUtils.getAllFieldsList(source.getClass());
        } else {
            allFieldsList = FieldUtils.getFieldsListWithAnnotation(source.getClass(), compareAnnotationType);
        }

        if (CollectionUtils.isEmpty(allFieldsList)) {
            return result;
        }

        for (Field field : allFieldsList) {

            ObjectCompareFieldResult diffValue = compareField(source, target, field, compareAnnotationType);
            if (null == diffValue) {
                continue;
            }

            diffs.add(diffValue);

        }
        return result;
    }

    /**
     * 比较两个对象的一个字段是否有差异
     *
     * @param source         源对象
     * @param target         目的对象
     * @param field          字段
     * @param annotationType 需要比较的注解
     * @return true 表示有差异, false表示无差异
     */
    private <T extends Annotation> ObjectCompareFieldResult compareField(Object source, Object target, Field field, final Class<T> annotationType) {

        // 如果是排除比较的字段, 不进行比较, 无差异
        if (excludeFieldNames.contains(field.getName())) {
            return null;
        }

        // 如果要求只比较相应的注解字段,而在指定字段上, 没有标注所需要比较的注解, 则不进行比较, 无差异
        if (null != annotationType) {
            T compareAnnotation = field.getAnnotation(annotationType);
            if (Objects.isNull(compareAnnotation)) {
                return null;
            }
        }

        // 获取两个对象此字段的值, 如果两个对象有一个此字段为null, 则不进行比较, 认定无差异
        Object sourceField = BeanUtil.getFieldValue(source, field.getName());
        Object targetField = BeanUtil.getFieldValue(target, field.getName());
        if (null == sourceField || null == targetField) {
            return null;
        }

        // 把两个对象此字段的值转成string
        String sourceValue = getToString(sourceField);
        String targetValue = getToString(targetField);

        String keyName = resolveFieldName(field);

        // BigDecimal 比较
        if (sourceField instanceof BigDecimal) {
            BigDecimal sourceFieldValue = (BigDecimal) sourceField;
            BigDecimal targetFieldValue = (BigDecimal) targetField;
            if (sourceFieldValue.compareTo(targetFieldValue) != 0) {
                return new ObjectCompareFieldResult(keyName, sourceValue, targetValue);
            }
        } else if (!sourceValue.equals(targetValue)) { // String比较两个值, 如果不相等
            return new ObjectCompareFieldResult(keyName, sourceValue, targetValue);
        }

        return null;
    }

    @Getter
    @Setter
    public static class ObjectCompareResult {

        private List<ObjectCompareFieldResult> diffFields;

        private boolean hasDifferences;

        public boolean getHasDifferences() {
            if (null == diffFields || diffFields.size() == 0) {
                return false;
            }
            return true;
        }

        @Override
        public String toString() {
            if (!getHasDifferences()) {
                return "";
            }

            StringBuffer buffer = new StringBuffer();
            buffer.append("信息变更:").append("\n");

            for (ObjectCompareFieldResult diffField : diffFields) {
                buffer.append(diffField.toString()).append("\n");
            }

            return buffer.toString();
        }
    }


    @Setter
    @Getter
    public static class ObjectCompareFieldResult {

        public ObjectCompareFieldResult(String fieldName, String sourceValue, String targetValue) {
            this.fieldName = fieldName;
            this.sourceValue = sourceValue;
            this.targetValue = targetValue;
        }

        private String fieldName;

        private String sourceValue;

        private String targetValue;

        @Override
        public String toString() {
            StringBuffer buffer = new StringBuffer();

            buffer.append(fieldName)
                .append("由")
                .append("[")
                .append(Objects.isNull(sourceValue) ? "--" : sourceValue)
                .append("]")
                .append("更改为")
                .append("[")
                .append(Objects.isNull(targetValue) ? "--" : targetValue)
                .append("]");
            return buffer.toString();
        }
    }

    private String getToString(Object object) {
        String sourceValue;
        if (object == null) {
            sourceValue = "";
        } else {
            if (object instanceof Date) {
                sourceValue = DateUtil.format((Date) object, DateUtil.NORM_DATETIME_PATTERN);
            } else if (object instanceof BigDecimal) {
                sourceValue = String.valueOf(((BigDecimal) object).doubleValue());
            } else {
                sourceValue = object.toString();
            }
        }
        return sourceValue;
    }

}
